﻿/********************************************************************************/
/* 	config.d																	*/
/*------------------------------------------------------------------------------*/
/*	製作		Kenmo / はてな日記からこぴぺ									*/
/*	製作開始　	2007/--/--														*/
/*	MAIL		--																*/
/*	URL			http://gamehell.g.hatena.ne.jp/kenmo/20070920					*/
/*																				*/
/*-更新履歴---------------------------------------------------------------------*/
/*	2008/02/19	動的なコンフィグファイルの再読み込みに対応						*/
/*-その他-----------------------------------------------------------------------*/
/*	なし																		*/
/********************************************************************************/

private import std.ctype;
private import std.file;
private import std.string;
private import std.date;

/**
 * INIファイル読み込みクラス
 */
class ConfigParser
{
private:
	int            _index;    // 現在のデータ番号
	char[][]       _indices;  // データ番号
	char[][char[]] _data;     // データ(キーと値)
	char[]         _filepath; // ファイルパス
	d_time         _fta;      // ファイルの最終更新時間(監視用)
public:
	this() {}
	void init()
	{
		// 初期化
		_index          = 0;
		_indices.length = 0;
		_data           = null;
		//foreach(k; _data) _data.remove(k);
	}
	void load(char[] filepath)
	{
		init();
		_filepath = filepath.dup; // ファイル名コピー
		// 読み込み
		auto file = cast(char[])read(filepath);
		// データを入れる
		foreach(b; split(file, "\n"))
		{
			int idx = find(b, '#');       // コメント文字を検索
			if(idx >= 0) b = b[0..idx];   // コメントを除去
			char[][] tmp = split(b, "="); // キーと値で分割
			if(tmp is null)    continue;
			if(tmp.length < 2) continue;
			char[] key = strip(tmp[0]);
			char[] val = strip(tmp[1]);
			set(key, val);
		}
		_data.rehash;
		
		// 監視用の更新時間
		d_time current_ftc , current_fta , current_ftm;
		getTimes(filepath , current_ftc , current_fta , current_ftm);
		
		_fta = current_fta;
	}
	char[] get(char[] key)        { checkKey(key);         return _data[key];             } // 文字列で取得
	int    getint(char[] key)     { char[] val = get(key); return checkInt(key, val);     } // 整数で取得
	float  getfloat(char[] key)   { char[] val = get(key); return checkFloat(key, val);   } // 浮動小数で取得
	bool   getboolean(char[] key) { char[] val = get(key); return checkBoolean(key, val); } // 真偽で取得
	// 文字列の追加
	void   set(char[] key, char[] val)
	{
		if(!(key in _data))
		{
			_index++;
			_indices ~= key;
		}
		_data[key] = val;
	}

	void   set(char[] key, int    val) { set(key, std.string.toString(val)); } // 整数の追加
	void   set(char[] key, float  val) { set(key, std.string.toString(val)); } // 浮動小数の追加
	void   set(char[] key, bool   val) { set(key, std.string.toString(val)); } // 真偽の追加
	void write(char[] filepath=null)
	{
		if(filepath is null) filepath = _filepath;
		std.file.write(filepath, "");
		foreach(k; _indices)
		{
			append(filepath, format("%s = %s\n", k, _data[k]));
		}
	}
	
	// configが古いかチェック
	bool isLatest(){
		d_time current_ftc , current_fta , current_ftm;
		getTimes(_filepath , current_ftc , current_fta , current_ftm);
		
		if(current_fta > _fta) return false;
		return true;
	}
	// configを読み直す
	void update(){
		load(_filepath);
	}
	void dump()
	{
		printf("{\n");
		foreach(k; _data.keys)
		{
			printf("  \"%.*s\" : \"%.*s\",\n", k, _data[k]);
		}
		printf("}\n");
	}
private:
	void checkKey(char[] key)
	{
		if(key in _data) return;
		throw new Error(format("ConfigParser.checkKey(): not exist key ... '%s'", key));
	}
	int checkInt(char[] key, char[] val)
	{
		foreach(c; val)
		{
			if(!isdigit(c)) throw new Error(format("not digit value ... '%s' -> '%s'", key, val));
		}
		return cast(int)atoi(val);
	}
	float checkFloat(char[] key, char[] val)
	{
		foreach(c; val)
		{
			if(!isdigit(c) && c != '.') throw new Error(format("not digit value ... '%s' -> '%s'", key, val));
		}
		return atof(val);
	}
	bool checkBoolean(char[] key, char[] val)
	{
		switch(val)
		{
		case "1", "yes", "true",  "on":  return true;
		case "0", "no",  "false", "off": return false;
		default: throw new Error(format("not boolean value ... '%s' -> '%s'", key, val));
		}
		return false;
	}
}
